{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#hide\n",
    "#skip\n",
    "! [ -e /content ] && pip install -Uqq fastai  # upgrade fastai on colab"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#all_slow\n",
    "#default_exp vision.utils"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "from fastai.torch_basics import *\n",
    "from fastai.data.all import *\n",
    "from fastai.vision.core import *\n",
    "from fastdownload import download_url\n",
    "from pathlib import Path"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#hide\n",
    "from nbdev.showdoc import *"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Vision utils\n",
    "\n",
    "> Some utils function to quickly download a bunch of images, check them and pre-resize them"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def _get_downloaded_image_filename(dest, name, suffix):\n",
    "    start_index = 1\n",
    "    candidate_name = name\n",
    "\n",
    "    while (dest/f\"{candidate_name}{suffix}\").is_file():\n",
    "        candidate_name = f\"{candidate_name}{start_index}\"\n",
    "        start_index += 1\n",
    "\n",
    "    return candidate_name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def _download_image_inner(dest, inp, timeout=4, preserve_filename=False):\n",
    "    i,url = inp\n",
    "    url = url.split(\"?\")[0]\n",
    "    url_path = Path(url)\n",
    "    suffix = url_path.suffix if url_path.suffix else '.jpg'\n",
    "    name = _get_downloaded_image_filename(dest, url_path.stem, suffix) if preserve_filename else f\"{i:08d}\"\n",
    "    try: download_url(url, dest/f\"{name}{suffix}\", show_progress=False, timeout=timeout)\n",
    "    except Exception as e: f\"Couldn't download {url}.\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "with tempfile.TemporaryDirectory() as d:\n",
    "    d = Path(d)\n",
    "    url = \"https://www.fast.ai/images/jh-head.jpg\"\n",
    "    _download_image_inner(d, (125,url))\n",
    "    assert (d/'00000125.jpg').is_file()\n",
    "\n",
    "with tempfile.TemporaryDirectory() as d:\n",
    "    d = Path(d)\n",
    "    url = \"https://www.fast.ai/images/jh-head.jpg\"\n",
    "\n",
    "    _download_image_inner(d, (125,url), preserve_filename=True)\n",
    "    assert (d/'jh-head.jpg').is_file()\n",
    "    assert not (d/'jh-head.jpg1').exists()\n",
    "\n",
    "    _download_image_inner(d, (125,url), preserve_filename=True)\n",
    "    assert (d/'jh-head.jpg').is_file()\n",
    "    assert (d/'jh-head1.jpg').is_file()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def download_images(dest, url_file=None, urls=None, max_pics=1000, n_workers=8, timeout=4, preserve_filename=False):\n",
    "    \"Download images listed in text file `url_file` to path `dest`, at most `max_pics`\"\n",
    "    if urls is None: urls = url_file.read_text().strip().split(\"\\n\")[:max_pics]\n",
    "    dest = Path(dest)\n",
    "    dest.mkdir(exist_ok=True)\n",
    "    parallel(partial(_download_image_inner, dest, timeout=timeout, preserve_filename=preserve_filename),\n",
    "             list(enumerate(urls)), n_workers=n_workers, threadpool=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "with tempfile.TemporaryDirectory() as d:\n",
    "    d = Path(d)\n",
    "    url_file = d/'urls.txt'\n",
    "    url_file.write_text(\"\\n\".join([f\"https://www.fast.ai/images/{n}\" for n in \"jh-head.jpg thomas.JPG sg-head.jpg\".split()]))\n",
    "    download_images(d, url_file)\n",
    "    for i in [0,2]: assert (d/f'0000000{i}.jpg').is_file()\n",
    "    assert (d/f'00000001.JPG').is_file()\n",
    "\n",
    "with tempfile.TemporaryDirectory() as d:\n",
    "    d = Path(d)\n",
    "    url_file = d/'urls.txt'\n",
    "    url_file.write_text(\"\\n\".join([f\"https://www.fast.ai/images/{n}\" for n in \"jh-head.jpg thomas.JPG sg-head.jpg\".split()]))\n",
    "    \n",
    "    download_images(d, url_file, preserve_filename=True)\n",
    "    assert (d/'jh-head.jpg').is_file()\n",
    "    assert (d/'thomas.JPG').is_file()\n",
    "    assert (d/'sg-head.jpg').is_file()\n",
    "    assert not (d/'jh-head1.jpg').exists()\n",
    "    assert not (d/'thomas1.JPG').exists()\n",
    "    assert not (d/'sg-head1.jpg').exists()\n",
    "    \n",
    "    download_images(d, url_file, preserve_filename=True)\n",
    "    assert (d/'jh-head.jpg').is_file()\n",
    "    assert (d/'thomas.JPG').is_file()\n",
    "    assert (d/'sg-head.jpg').is_file()\n",
    "    assert (d/'jh-head1.jpg').is_file()\n",
    "    assert (d/'thomas1.JPG').is_file()\n",
    "    assert (d/'sg-head1.jpg').is_file()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def resize_to(img, targ_sz, use_min=False):\n",
    "    \"Size to resize to, to hit `targ_sz` at same aspect ratio, in PIL coords (i.e w*h)\"\n",
    "    w,h = img.size\n",
    "    min_sz = (min if use_min else max)(w,h)\n",
    "    ratio = targ_sz/min_sz\n",
    "    return int(w*ratio),int(h*ratio)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class _FakeImg():\n",
    "    def __init__(self, size): self.size=size\n",
    "img = _FakeImg((200,500))\n",
    "\n",
    "test_eq(resize_to(img, 400), [160,400])\n",
    "test_eq(resize_to(img, 400, use_min=True), [400,1000])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def verify_image(fn):\n",
    "    \"Confirm that `fn` can be opened\"\n",
    "    try:\n",
    "        im = Image.open(fn)\n",
    "        im.draft(im.mode, (32,32))\n",
    "        im.load()\n",
    "        return True\n",
    "    except: return False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def verify_images(fns):\n",
    "    \"Find images in `fns` that can't be opened\"\n",
    "    return L(fns[i] for i,o in enumerate(parallel(verify_image, fns)) if not o)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def resize_image(file, dest, max_size=None, n_channels=3, ext=None,\n",
    "                 img_format=None, resample=Image.BILINEAR, resume=False, **kwargs ):\n",
    "    \"Resize file to dest to max_size\"\n",
    "    dest = Path(dest)\n",
    "    dest_fname = dest/file.name\n",
    "    if resume and dest_fname.exists(): return\n",
    "    if verify_image(file):\n",
    "        img = Image.open(file)\n",
    "        imgarr = np.array(img)\n",
    "        img_channels = 1 if len(imgarr.shape) == 2 else imgarr.shape[2]\n",
    "        if (max_size is not None and (img.height > max_size or img.width > max_size)) or img_channels != n_channels:\n",
    "            if ext is not None: dest_fname=dest_fname.with_suffix(ext)\n",
    "            if max_size is not None:\n",
    "                new_sz = resize_to(img, max_size)\n",
    "                img = img.resize(new_sz, resample=resample)\n",
    "            if n_channels == 3: img = img.convert(\"RGB\")\n",
    "            img.save(dest_fname, img_format, **kwargs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "file = Path('images/puppy.jpg')\n",
    "dest = Path('.')\n",
    "resize_image(file, max_size=400, dest=dest)\n",
    "im = Image.open(dest/file.name)\n",
    "test_eq(im.shape[1],400)\n",
    "(dest/file.name).unlink()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def resize_images(path, max_workers=defaults.cpus, max_size=None, recurse=False,\n",
    "                  dest=Path('.'), n_channels=3, ext=None, img_format=None, resample=Image.BILINEAR,\n",
    "                  resume=None, **kwargs):\n",
    "    \"Resize files on path recursively to dest to max_size\"\n",
    "    path = Path(path)\n",
    "    if resume is None and dest != Path('.'): resume=False\n",
    "    os.makedirs(dest, exist_ok=True)\n",
    "    files = get_image_files(path, recurse=recurse)\n",
    "    parallel(resize_image, files, max_workers=max_workers, max_size=max_size, dest=dest, n_channels=n_channels, ext=ext,\n",
    "                   img_format=img_format, resample=resample, resume=resume, **kwargs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/jhoward/miniconda3/lib/python3.8/site-packages/PIL/Image.py:973: UserWarning: Palette images with Transparency expressed in bytes should be converted to RGBA images\n",
      "  warnings.warn(\n"
     ]
    }
   ],
   "source": [
    "with tempfile.TemporaryDirectory() as d:\n",
    "    dest = Path(d)/'resized_images'\n",
    "    resize_images('images', max_size=100, dest=dest)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Export -"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Converted 00_torch_core.ipynb.\n",
      "Converted 01_layers.ipynb.\n",
      "Converted 01a_losses.ipynb.\n",
      "Converted 02_data.load.ipynb.\n",
      "Converted 03_data.core.ipynb.\n",
      "Converted 04_data.external.ipynb.\n",
      "Converted 05_data.transforms.ipynb.\n",
      "Converted 06_data.block.ipynb.\n",
      "Converted 07_vision.core.ipynb.\n",
      "Converted 08_vision.data.ipynb.\n",
      "Converted 09_vision.augment.ipynb.\n",
      "Converted 09b_vision.utils.ipynb.\n",
      "Converted 09c_vision.widgets.ipynb.\n",
      "Converted 10_tutorial.pets.ipynb.\n",
      "Converted 10b_tutorial.albumentations.ipynb.\n",
      "Converted 11_vision.models.xresnet.ipynb.\n",
      "Converted 12_optimizer.ipynb.\n",
      "Converted 13_callback.core.ipynb.\n",
      "Converted 13a_learner.ipynb.\n",
      "Converted 13b_metrics.ipynb.\n",
      "Converted 14_callback.schedule.ipynb.\n",
      "Converted 14a_callback.data.ipynb.\n",
      "Converted 15_callback.hook.ipynb.\n",
      "Converted 15a_vision.models.unet.ipynb.\n",
      "Converted 16_callback.progress.ipynb.\n",
      "Converted 17_callback.tracker.ipynb.\n",
      "Converted 18_callback.fp16.ipynb.\n",
      "Converted 18a_callback.training.ipynb.\n",
      "Converted 18b_callback.preds.ipynb.\n",
      "Converted 19_callback.mixup.ipynb.\n",
      "Converted 20_interpret.ipynb.\n",
      "Converted 20a_distributed.ipynb.\n",
      "Converted 21_vision.learner.ipynb.\n",
      "Converted 22_tutorial.imagenette.ipynb.\n",
      "Converted 23_tutorial.vision.ipynb.\n",
      "Converted 24_tutorial.image_sequence.ipynb.\n",
      "Converted 24_tutorial.siamese.ipynb.\n",
      "Converted 24_vision.gan.ipynb.\n",
      "Converted 30_text.core.ipynb.\n",
      "Converted 31_text.data.ipynb.\n",
      "Converted 32_text.models.awdlstm.ipynb.\n",
      "Converted 33_text.models.core.ipynb.\n",
      "Converted 34_callback.rnn.ipynb.\n",
      "Converted 35_tutorial.wikitext.ipynb.\n",
      "Converted 37_text.learner.ipynb.\n",
      "Converted 38_tutorial.text.ipynb.\n",
      "Converted 39_tutorial.transformers.ipynb.\n",
      "Converted 40_tabular.core.ipynb.\n",
      "Converted 41_tabular.data.ipynb.\n",
      "Converted 42_tabular.model.ipynb.\n",
      "Converted 43_tabular.learner.ipynb.\n",
      "Converted 44_tutorial.tabular.ipynb.\n",
      "Converted 45_collab.ipynb.\n",
      "Converted 46_tutorial.collab.ipynb.\n",
      "Converted 50_tutorial.datablock.ipynb.\n",
      "Converted 60_medical.imaging.ipynb.\n",
      "Converted 61_tutorial.medical_imaging.ipynb.\n",
      "Converted 65_medical.text.ipynb.\n",
      "Converted 70_callback.wandb.ipynb.\n",
      "Converted 71_callback.tensorboard.ipynb.\n",
      "Converted 72_callback.neptune.ipynb.\n",
      "Converted 73_callback.captum.ipynb.\n",
      "Converted 74_callback.azureml.ipynb.\n",
      "Converted 97_test_utils.ipynb.\n",
      "Converted 99_pytorch_doc.ipynb.\n",
      "Converted dev-setup.ipynb.\n",
      "Converted app_examples.ipynb.\n",
      "Converted camvid.ipynb.\n",
      "Converted migrating_catalyst.ipynb.\n",
      "Converted migrating_ignite.ipynb.\n",
      "Converted migrating_lightning.ipynb.\n",
      "Converted migrating_pytorch.ipynb.\n",
      "Converted ulmfit.ipynb.\n",
      "Converted index.ipynb.\n",
      "Converted quick_start.ipynb.\n",
      "Converted tutorial.ipynb.\n"
     ]
    }
   ],
   "source": [
    "#hide\n",
    "from nbdev.export import notebook2script\n",
    "notebook2script()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "jupytext": {
   "split_at_heading": true
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
